require 'action_view/helpers/tags'
require 'easy_form/map_type'
require 'easy_form/tags'

module EasyForm
  class FormBuilder < ActionView::Helpers::FormBuilder
    attr_reader :template, :object_name, :object, :wrapper
    extend MapType

    #数据库真实存在的类型
    map_type :text, to: EasyForm::Inputs::TextInput
    map_type :string, to: EasyForm::Inputs::StringInput
    map_type :boolean, to: EasyForm::Inputs::BooleanInput
    map_type :float, :integer, :decimal, to: EasyForm::Inputs::NumericInput
    map_type :date, to: EasyForm::Inputs::DateInput
    map_type :datetime, to: EasyForm::Inputs::DatetimeInput
    #as指定的类型
    map_type :password, to: EasyForm::Inputs::PasswordInput
    map_type :radio, to: EasyForm::Inputs::RadioInput
    map_type :select, to: EasyForm::Inputs::SelectInput
    map_type :file, to: EasyForm::Inputs::FileInput
    map_type :hidden, to: EasyForm::Inputs::HiddenInput


    def initialize(*)
      super
      @object   = convert_to_model(@object)
      @wrapper  = EasyForm.wrapper(@options[:wrapper] || EasyForm.default_wrapper)
    end


    def input(attribute_name, options = {}, &block)
      input = find_input(attribute_name, options, &block)
      wrapper = find_wrapper(input.input_type, options)
      wrapper.render(input)
    end

    #原来的button方法重命名
    alias_method :button_button, :button
    def button(type, *args, &block)
      options = args.extract_options!.dup
      options[:class] = [EasyForm.button_class, options[:class]].compact
      wrapper_options = options.delete(:btn_wrapper)

      args << options
      content = if respond_to?("#{type}_button") #button_button调用原来的方法
                  send("#{type}_button", *args, &block)
                else
                  send(type, *args, &block)
                end
      return content if wrapper_options.blank?
      tag = wrapper_options.delete(:tag) || :div
      template.content_tag tag, content, wrapper_options
    end

    def label(attribute_name, *args)
      return super if args.first.is_a?(String) || block_given?

      options = args.extract_options!.dup
      options[:label_html] = options.except(:as, :label, :label_text, :required)

      column = find_attribute_column(attribute_name)
      input_type = default_input_type(attribute_name, column, options)
      EasyForm::Inputs::Base.new(self, attribute_name, column, input_type, options).label
    end

    def error_notification(options = {})
      EasyForm::ErrorNotification.new(self, options).render
    end

    def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
      EasyForm::Tags::CollectionRadioButtons.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
    end

    def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
      EasyForm::Tags::CollectionCheckBoxes.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
    end


    def lookup_model_names
      @lookup_model_names ||= begin
        child_index = options[:child_index]
        names = object_name.to_s.scan(/(?!\d)\w+/).flatten
        names.delete(child_index) if child_index
        names.each {|name| name.gsub!('_attributes', '')}
        names.freeze
      end
    end
    ACTIONS = {
        'create' => 'new',
        'update' => 'edit'
    }
    def lookup_action
      @lookup_action ||= begin
        action = template.controller && template.controller.action_name
        return unless action
        action = action.to_s
        ACTIONS[action] || action
      end
    end

    private
    def find_input(attribute_name, options, &block)
      column = find_attribute_column(attribute_name)
      input_type = default_input_type(attribute_name, column, options)

      if block_given?
        EasyForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block)
      else
        find_mapping(input_type).new(self, attribute_name, column, input_type, options)
      end
    end

    def find_mapping(input_type)
      discovery_cache[input_type] ||=
          if mapping = self.class.mappings[input_type]
            #TODO 自定义input
            mapping
          else
            #TODO
            raise "No input found for #{input_type}"
          end
    end

    def find_wrapper(input_type, options)
      name = options[:wrapper] || find_wrapper_mapping(input_type)
      if name
        name.respond_to?(:render) ? name : EasyForm.wrapper(name)
      else
        wrapper
      end
    end

    def find_wrapper_mapping(input_type)
      if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
        options[:wrapper_mappings][input_type]
      else
        EasyForm.wrapper_mappings && EasyForm.wrapper_mappings[input_type]
      end

    end

    def default_input_type(attribute_name, column, options)
      return options[:as].to_sym if options[:as]
      return :select if options[:collection]
      input_type = column.try(:type)

      case input_type
      when :timestamp
        :datetime
      when :string, :citext, nil
        file_method?(attribute_name) ? :file : (input_type || :string)
      else
        input_type
      end
    end
    def find_attribute_column(attribute_name)
      return unless @object.has_attribute?(attribute_name)
      #type_for_attribute方法参数传symbol时，取到了type是空的，必需要传字符串
      return @object.type_for_attribute(attribute_name.to_s) if @object.respond_to?(:type_for_attribute)
      return @object.column_for_attribute(attribute_name) if @object.respond_to?(:column_for_attribute)
    end

    # - `#{attribute_name}_attachment` - ActiveStorage >= `5.2` and Refile >= `0.2.0` <= `0.4.0`
    # - `remote_#{attribute_name}_url` - Refile >= `0.3.0` and CarrierWave >= `0.2.2`
    # - `#{attribute_name}_attacher` - Refile >= `0.4.0` and Shrine >= `0.9.0`
    # - `#{attribute_name}_file_name` - Paperclip ~> `2.0` (added for backwards compatibility)
    #检查字段是不是文件型，通过验证是否存在文件上传插件指定的方法来判断
    def file_method?(attribute_name)
      @object.respond_to?("#{attribute_name}_attachment") ||
          @object.respond_to?("remote_#{attribute_name}_url") ||
          @object.respond_to?("#{attribute_name}_attacher") ||
          @object.respond_to?("#{attribute_name}_file_name")
    end

    def self.discovery_cache
      @discovery_cache ||= {}
    end
    def discovery_cache
      if EasyForm.cache_discovery
        self.class.discovery_cache
      else
        @discovery_cache ||= {}
      end
    end

    def objectify_options(options)
      @default_options.merge(options.merge(object: @object))
    end
  end
end